#include <iostream>

#include "Color.h"
#include "CmdParser.h"
#include "Parameter.h"
#include "BoolParameter.h"
#include "VariableParameter.h"
#include "ListParameter.h"
#include "OperandParameter.h"
#include "ExtensionParameter.h"

namespace czj {
namespace util {

CmdParser::CmdParser(const char *_cmd)
		: cmd_(_cmd), helpKeys_(), parameters_(), operandParameters_(), extensionParameter_() {
}

CmdParser::~CmdParser() {
}

void CmdParser::bindHelpKey(const char *_key) {
	helpKeys_.push_back(_key);
}

bool CmdParser::addBool(int _id, const char *_describe) {
	return parameters_.insert(std::map<int, std::unique_ptr<Parameter>>::value_type(_id, std::unique_ptr<Parameter>(new BoolParameter(_describe)))).second;
}

bool CmdParser::bindBoolKey(int _id, const char *_key) {
	auto iterator = parameters_.find(_id);
	if (iterator == parameters_.cend() || iterator->second->getType() != Parameter::BOOL) {
		return false;
	}
	return ((BoolParameter *) iterator->second.get())->bindKey(_key);
}

bool CmdParser::getBool(int _id) const {
	return ((BoolParameter *) parameters_.at(_id).get())->getValue();
}

bool CmdParser::addVariable(int _id, const char *_describe, bool _nullable) {
	return parameters_.insert(std::pair<int, std::unique_ptr<Parameter>>(_id, std::unique_ptr<Parameter>(new VariableParameter(_describe, _nullable)))).second;
}

bool CmdParser::bindVariableKey(int _id, const char *_key, const char *_hint) {
	auto iterator = parameters_.find(_id);
	if (iterator == parameters_.cend() || iterator->second->getType() != Parameter::VARIABLE) {
		return false;
	}
	return ((VariableParameter *) iterator->second.get())->bindKey(_key, _hint);
}

bool CmdParser::bindVariableExpression(int _id, const char *_key, const char *_operator, const char *_hint, const char *_default) {
	auto iterator = parameters_.find(_id);
	if (iterator == parameters_.cend() || iterator->second->getType() != Parameter::VARIABLE) {
		return false;
	}
	return ((VariableParameter *) iterator->second.get())->bindExpression(_key, _operator, _hint, _default);
}

const std::string &CmdParser::getVariable(int _id) const {
	return ((VariableParameter *) parameters_.at(_id).get())->getValue();
}

bool CmdParser::addList(int _id, const char *_describe, bool _nullable) {
	return parameters_.insert(std::pair<int, std::unique_ptr<Parameter>>(_id, std::unique_ptr<Parameter>(new ListParameter(_describe, _nullable)))).second;
}

bool CmdParser::bindListKey(int _id, const char *_key, const char *_hint) {
	auto iterator = parameters_.find(_id);
	if (iterator == parameters_.cend() || iterator->second->getType() != Parameter::LIST) {
		return false;
	}
	return ((ListParameter *) iterator->second.get())->bindKey(_key, _hint);
}

bool CmdParser::bindListExpression(int _id, const char *_key, const char *_operator, const char *_hint, const char *_default) {
	auto iterator = parameters_.find(_id);
	if (iterator == parameters_.cend() || iterator->second->getType() != Parameter::LIST) {
		return false;
	}
	return ((ListParameter *) iterator->second.get())->bindExpression(_key, _operator, _hint, _default);
}

const std::list<std::string> &CmdParser::getList(int _id) const {
	return ((ListParameter *) parameters_.at(_id).get())->getValues();
}

void CmdParser::addOperand(int _id, const char *_describe, const char *_hint) {
	operandParameters_[_id] = std::unique_ptr<OperandParameter>(new OperandParameter(_describe, _hint));
}

const std::string &CmdParser::getOperand(int _id) const {
	return operandParameters_.at(_id).get()->getValue();
}

void CmdParser::enableExtension(const char *_describe, const char *_hint) {
	extensionParameter_ = std::unique_ptr<ExtensionParameter>(new ExtensionParameter(_describe, _hint));
}

const std::list<std::string> &CmdParser::getExtension() const {
	return extensionParameter_->getValues();
}

void CmdParser::help() const {
	std::cout << COLOR_PURPLE << "[USE]" << COLOR_NULL << " " << COLOR_AZURE << cmd_ << COLOR_NULL;
	for (auto const &pair : parameters_) {
		auto finished = false;
		auto const &parameter = pair.second.get();
		switch (parameter->getType()) {
			case Parameter::BOOL:
			case Parameter::VARIABLE:
			case Parameter::LIST:
				std::cout << " " << COLOR_GREEN << "options[]" << COLOR_NULL;
				finished = true;
				break;
			default:
				break;
		}
		if (finished) {
			break;
		}
	}
	for (auto const &pair : operandParameters_) {
		std::cout << " " << COLOR_GREEN << pair.second.get()->getHint() << COLOR_NULL;
	}
	if (extensionParameter_ != nullptr) {
		std::cout << " " << COLOR_GREEN << extensionParameter_->getHint() << "[]" << COLOR_NULL;
	}
	std::cout << std::endl << std::endl;
	for (auto iterator = helpKeys_.cbegin(); iterator != helpKeys_.cend(); ++iterator) {
		if (iterator == helpKeys_.cbegin()) {
			std::cout << "    ";
		}
		else {
			std::cout << ", ";
		}
		std::cout << COLOR_GREEN << *iterator << COLOR_NULL;
	}
	if (!helpKeys_.empty()) {
		std::cout << std::endl << "        Display this information" << std::endl << std::endl;
	}
	for (auto const &pair : parameters_) {
		auto const &parameter = pair.second.get();
		switch (parameter->getType()) {
			case Parameter::BOOL: {
				auto const &boolParameter = (BoolParameter *) parameter;
				auto const &hints = boolParameter->getHints();
				for (auto iterator = hints.cbegin(); iterator != hints.cend(); ++iterator) {
					if (iterator == hints.cbegin()) {
						std::cout << COLOR_YELLOW << "[?]" << COLOR_NULL << " ";
					}
					else {
						std::cout << ", ";
					}
					std::cout << COLOR_GREEN << *iterator << COLOR_NULL;
				}
				if (!hints.empty()) {
					std::cout << std::endl << "        " << boolParameter->getDescribe() << std::endl;
				}
				break;
			}
			case Parameter::VARIABLE: {
				auto const &variableParameter = (VariableParameter *) parameter;
				auto const &hints = variableParameter->getHints();
				for (auto iterator = hints.cbegin(); iterator != hints.cend(); ++iterator) {
					if (iterator == hints.cbegin()) {
						std::cout << COLOR_YELLOW << (variableParameter->isNullable() ? "[?]" : "[!]") << COLOR_NULL << " ";
					}
					else {
						std::cout << ", ";
					}
					std::cout << COLOR_GREEN << *iterator << COLOR_NULL;
				}
				if (!hints.empty()) {
					std::cout << std::endl << "        " << variableParameter->getDescribe() << std::endl;
				}
				break;
			}
			case Parameter::LIST: {
				auto const &listParameter = (ListParameter *) parameter;
				auto const &hints = listParameter->getHints();
				for (auto iterator = hints.cbegin(); iterator != hints.cend(); ++iterator) {
					if (iterator == hints.cbegin()) {
						std::cout << COLOR_YELLOW << (listParameter->isNullable() ? "[*]" : "[+]") << COLOR_NULL << " ";
					}
					else {
						std::cout << ", ";
					}
					std::cout << COLOR_GREEN << *iterator << COLOR_NULL;
				}
				if (!hints.empty()) {
					std::cout << std::endl << "        " << listParameter->getDescribe() << std::endl;
				}
				break;
			}
			default: {
				break;
			}
		}
	}
	if (!parameters_.empty()) {
		std::cout << std::endl;
	}
	for (auto const &pair : operandParameters_) {
		auto const &operandParameter = pair.second;
		std::cout << "    " << COLOR_GREEN << operandParameter->getHint() << COLOR_NULL << std::endl;
		std::cout << "        " << operandParameter->getDescribe() << std::endl;
	}
	if (extensionParameter_ != nullptr) {
		std::cout << "    " << COLOR_GREEN << extensionParameter_->getHint() << "[]" << COLOR_NULL << std::endl;
		std::cout << "        " << extensionParameter_->getDescribe() << std::endl;
	}
	if (!operandParameters_.empty() || extensionParameter_ != nullptr) {
		std::cout << std::endl;
	}
}

bool CmdParser::parse(int _length, const char *const *_arguments) {
	auto iterator = operandParameters_.cbegin();
	auto helped = false;
	for (int i = 0; i < _length; ++i) {
		auto parsed = false;
		for (auto const &helpKey: helpKeys_) {
			if (helpKey == _arguments[i]) {
				if (!helped) {
					help();
					helped = true;
				}
				parsed = true;
			}
		}
		if (parsed) {
			continue;
		}
		for (auto const &pair : parameters_) {
			auto const &parameter = pair.second.get();
			switch (parameter->getType()) {
				case Parameter::BOOL: {
					auto boolParameter = (BoolParameter *) parameter;
					if (boolParameter->matchKey(_arguments[i])) {
						parsed = true;
					}
					break;
				}
				case Parameter::VARIABLE: {
					auto variableParameter = (VariableParameter *) parameter;
					if (i + 1 < _length && variableParameter->matchKey(_arguments[i], _arguments[i + 1])) {
						parsed = true;
						++i;
					}
					else if (variableParameter->matchExpression(_arguments[i])) {
						parsed = true;
					}
					break;
				}
				case Parameter::LIST: {
					auto listParameter = (ListParameter *) parameter;
					if (i + 1 < _length && listParameter->matchKey(_arguments[i], _arguments[i + 1])) {
						parsed = true;
						++i;
					}
					else if (listParameter->matchExpression(_arguments[i])) {
						parsed = true;
					}
					break;
				}
				default: {
					break;
				}
			}
			if (parsed) {
				break;
			}
		}
		if (parsed) {
			continue;
		}
		if (iterator != operandParameters_.cend()) {
			iterator->second->setValue(_arguments[i]);
			parsed = true;
			++iterator;
		}
		if (parsed) {
			continue;
		}
		if (extensionParameter_ == nullptr) {
			return false;
		}
		for (; i < _length; ++i) {
			extensionParameter_->addValue(_arguments[i]);
		}
	}
	for (auto const &pair : parameters_) {
		auto const &parameter = pair.second.get();
		switch (parameter->getType()) {
			case Parameter::VARIABLE: {
				auto variableParameter = (const VariableParameter *) parameter;
				if (!variableParameter->isNullable() && variableParameter->isNull()) {
					return false;
				}
				break;
			}
			case Parameter::LIST: {
				auto listParameter = (const ListParameter *) parameter;
				if (!listParameter->isNullable() && listParameter->getValues().empty()) {
					return false;
				}
				break;
			}
			default: {
				break;
			}
		}
	}
	for (auto const &pair : operandParameters_) {
		if (pair.second.get()->isNull()) {
			return false;
		}
	}
	return true;
}

} // util
} // czj
